Ejercicio de machine learning 1: detección de conexiones cibernéticas intrusivas

En este ejercicio (mucho menos guiado que los anteriores), tu objetivo es construir un detector de intrusiones cibernéticas.

Para ello, utilizarás un conocido (pero modificado) dataset conocido como el Dataset KDD-CUP-99. Deberás construir un detector de intrusiones de red: un modelo de machine learning que sea capaz de distinguir entre conexiones "maliciosas" (attacks) y conexiones "buenas" (normales, corrientes y legítimas).

El dataset

El dataset contiene casi 20.000 conexiones (una por fila/observación) recopiladas a lo largo de un período de tiempo, y auditadas por expertos en ciberseguridad; de forma que los expertos han etiquetado cada conexión como attack o normal en base a si es una conexión maliciosa o una normal. Dado que se trata de una tarea muy rutinaria y aburrida, sería ideal utilizar los datos ya etiquetados por expertos para ser capaz de entrenar un modelo de machine learning que pueda predecir en conexiones futuras si se tratan de ataques o no, de forma automática.

Cada fila del CSV (KDD_dataset.csv, separado por punto y coma) contiene por tanto información de una conexión. Aparte de aparecer si se trata o no de un ataque (que es la variable a predecir type, la última columna), cada conexión contiene muchas columnas con información adicional de la conexión. Puedes (¡y debes!) usar estas columnas, con el objetivo de predecir lo mejor posible si dicha conexión se trata de un ataque o no en base a dichas features.

El dataset contiene por tanto 46 columnas: 45 atributos de la conexión, y la variable a predecir type. La descripción de lo que significa cada columna puedes verla en la siguiente tabla (la cual he construido a partir de la información oficial del dataset):

columna (o familia de columnas) descripción
duration length (number of seconds) of the connection
protocol_type type of the protocol, e.g. tcp, udp, etc.
service network service on the destination, e.g., http, telnet, etc.
src_bytes number of data bytes from source to destination
dst_bytes number of data bytes from destination to source
flag normal or error status of the connection
land 1 if connection is from/to the same host/port; 0 otherwise
wrong_fragment number of "wrong" fragments
urgent number of urgent packets
hot number of "hot" indicators
num_failed_logins number of failed login attempts
logged_in 1 if successfully logged in; 0 otherwise
num_compromised number of "compromised" conditions
root_shell 1 if root shell is obtained; 0 otherwise
su_attempted 1 if "su root" command attempted; 0 otherwise
num_root number of "root" accesses
num_file_creations number of file creation operations
num_shells number of shell prompts
num_access_files number of operations on access control files
num_outbound_cmds number of outbound commands in an ftp session
is_hot_login 1 if the login belongs to the "hot" list; 0 otherwise
is_guest_login 1 if the login is a "guest" login; 0 otherwise
count number of connections to the same host as the current connection in the past two seconds
srv_count number of connections to the same service as the current connection in the past two seconds
serror_rate % of same-host connections that have "SYN" errors
srv_serror_rate % of same-service connections that have "REJ" errors
rerror_rate % of same-host connections that have "REJ" errors
srv_rerror_rate % of same-service connections that have "REJ" errors
same_srv_rate % of connections to the same service
diff_srv_rate % of connections to different services
srv_diff_host_rate % of connections to different hosts

Si miras la tabla de arriba, verás que no tiene 45 entradas, sino que son algunas menos. Esto se debe a que, a partir de alguna de las entradas, hemos generado por ti más de una feature que pueden usar tus modelos. Por ejemplo: en la tabla de arriba puedes ver que hay una entrada que es protocol_type; que es, según la descripción, el tipo de protocolo de red que ha usado la conexión: tcp, udp, etcétera. Nosotros hemos hecho One-Hot-Encoding de este tipo de variables por ti; de modo que en vez de encontrar una columna que es protocol_type, te vas a encontrar protocol_type__tcp, protocol_type__udp y protocol_type__icmp; y cada una de ellas tiene valor de 1 (si se trata efectivamente de una conexión de ese tipo), y de 0 si no e así.

Por ejemplo: para las primeras dos conexiones del dataset nos encontramos:

protocol_type__tcp protocol_type__udp protocol_type__icmp
0 1 0
1 0 0

Lo cual quiere decir que la primera conexión se trata de una udp; y la segunda de una tcp. De esta forma, lo que originariamente sería solo una feature de tipo de dato string (protocol_type, que tomaría valores de strings como tcp, udp o icmp), ahora son varias features; pero numéricas, que podemos meter directamente en los modelos de machine learning. Puedes leer más sobre One-Hot-Encoding en la versión extendida de las diapositivas de machine learning.

Sobra decir que realmente no tienes por qué saber nada de ciberseguridad para poder hacerlo, ni tienes que entender qué es exactamente cada feature; asumiendo que las features están bien recopiladas, una de las gracias del machine learning es que puedes ser capaz de realizar acciones basadas en datos sin tener conocimiento extenso del campo donde lo aplicas.

Objetivos

  1. Pensar en qué ténica de machine learning debes utilizar. ¿Se trata de aprendizaje supervisado, o no supervisado? ¿Y dentro de una de esas dos?
  2. Dependiendo de lo que respondas a la pregunta anterior, es posible que debas seguir alguno de los workflows que vimos en la teoría de machine learning en clase... Análisis exploratorio de los datos, separar en train y test, separar también validación (o usar cross-validation), probar distintos modelos e hiperparámetros... Lo que necesites para construir un modelo que sea capaz de detectar lo mejor posible si una conexión se trata de una intrusión o no.
  3. Una vez construido el modelo, no esperes que el equipo de ciberseguridad te dé luz verde para empezar a usarlo mañana mismo. Tanto tú como tu modelo tenéis que ganaros la confianza de ser realmente útiles. Para ello, va a ser necesario que demuestres cómo de bien podrá, tu modelo, detectar nuevas intrusiones a futuro. O, dicho de otra forma, mostrar cómo de bueno/malo esperamos que sea el modelo a futuro.
  4. Justo esta mañana hemos recibido tres registros de nuevas conexiones, que el departamento de ciberseguridad no tiene tiempo ahora mismo para comprobar. ¡Es la oportunidad ideal para comenzar a usar tu modelo! Genera predicciones para esas tres nuevas observaciones (que evidentemente no contienen la etiqueta type, ya que de momento desconocemos si son intrusiones o no), las cuales están disponibles en el archivo nuevas_conexiones.csv y guárdalas de vuelta en un archivo CSV que se llame predicciones_nuevas_conexiones.csv que incluya todas las features de dichas observaciones junto con una nueva columna llamada prediccion_ml, que contenga las predicciones.
Como es normal, la mayoría de conexiones son "buenas", es decir, no son ataques. Así podrás ver que ocurre en el dataset. Es posible que este fenómeno influya en la manera en la que quieres medir cómo comparar qué modelo de los que pruebes lo hace mejor...
Para el paso 4, verás que el formato del CSV de las nuevas conexiones es ligeramente distinto al del dataset original. A la hora de hacer predicciones, es importante "pasar" al modelo las features en el mismo orden que con el que entrenó; si no, ¡confundirás al modelo porque pensará que una feature es realmente otra, y viceversa!

Y nada más. ¡Buena suerte!

0. Importación de Bibliotecas

Directriz o comando magic para que los gráficos los deje metidos dentro de este notebook y no en una pantalla aparte:

Se configuran un par de opciones:

1. Carga de datos

Se lee el dataset desde el directorio de trabajo:

Se observa cómo es el dataset:

Se aprecian 19534 filas o conexiones y para cada una de ellas 45 columnas o variables asociadas (sus atributos) con sus correpondientes datos. La variable a predecir es la última columna número 46: type

Se trata de un caso de Machine Learning de Aprendizaje Supervisado (Supervised Learning) y dentro de éste, de Clasificación. Se tendrá como objetivo la construcción de un modelo predictivo de la variable a predecir explícita type, siendo ésta una categoría o clase . Ésta podrá tomar los valores attack o normal, siendo los primeros característicos de conexiones malicionas y los segundos, de conexiones buenas. Se está por lo tanto no ante un problema de clasificación multiclase, sino de clasificación binaria.

2. EDA (Exploratory Data Analysis): análisis exploratorio de los datos

Se trata de un dataset del que se sabe relativamente poco y que se podría considerar como "nuevo" para el usuario, por lo que se considera conveniente y oportuno realizar un análisis descriptivo de los datos de las variables mediante la estadística. Se comienza con el método .describe():

Realizando una exploración global de los estadísticos y de sus correspondientes valores por variable, a priori ningún dato produce una especial llamada de atención, que pueda activar la alarma de la existencia de algún valor o situación irregular o singular, merecedora de un análisis más profundo.

Se usa el método isnull() y sum() para saber si existen registros nulos en el dataframe por columna:

Se constata la no presencia de valores nulos en el dataframe.

Se considera interesante tener una visión de qué proporción de conexiones maliciosas y buenas se tiene:

Se constata que la mayoría de las conexiones son buenas frente a una minoría de maliciosas. En el contexto de problemas de clasificación, se dice que un conjunto de datos no está balanceado si una de las clases (mayoritaria y en este caso conexiones buenas) está sensiblemente más representada que el resto de clases (minoritarías y en este caso conexiones maliciosas). Esto representa un dato importante a tener en cuenta a la hora del desarrollo posterior del análisis y de la construcción del mejor modelo de predicción.

Se obtiene una lista con el nombre de las columnas de las features del dataframe: columns_names_features_list para su posible uso de aquí en adelante:

Las features son: 'duration', 'protocol_typetcp', 'protocol_typeudp', 'protocol_typeicmp', 'servicehttp', 'serviceprivate', 'servicedomain_u', 'servicesmtp', 'serviceftp_data', 'servicetelnet', 'serviceftp', 'serviceother', 'src_bytes', 'dst_bytes', 'flagSF', 'flagS0', 'flagREJ', 'flagRSTR', 'flagRSTO', 'flag__OTH', 'land', 'wrong_fragment', 'urgent', 'hot', 'num_failed_logins', 'logged_in', 'num_compromised', 'root_shell', 'su_attempted', 'num_root', 'num_file_creations', 'num_shells', 'num_access_files', 'num_outbound_cmds', 'is_host_login', 'is_guest_login', 'count', 'srv_count', 'serror_rate', 'srv_serror_rate', 'rerror_rate', 'srv_rerror_rate', 'same_srv_rate', 'diff_srv_rate', 'srv_diff_host_rate'

Se va a intentar ser sistemático en el análisis más profundo que se desarrollará a partir de lo obtenido hasta aquí. ¿De qué forma? Separando en tres categorías las features para realizar las visualizaciones más adecuadas de las mismas. A saber, las "number of", las que tienen valores 0 o 1 (y 2!: 'su_attempted') y los porcentajes en tanto por uno.

1.- "Number of"

1.1.- 'duration'

El método "describe" ya apuntaba que el grueso de los valores de duración en segundos de la conexión estaba alrededor de cero, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a este valor. Llaman la atención quizá dos aspectos. Por un lado, la gran cantidad de valores "outliers" de duración de hasta un máximo de 42616 segundos (casi 12 horas) que arroja la visualización del diagrama de bigotes y por otra, visualizando los histogramas se aprecia que para valores de duración de conexión altos aparecen un mayor número de conexiones maliciosas que buenas.

1.2.- 'src_bytes'

El método "describe" ya apuntaba que el grueso de los valores del número de bytes de datos desde la fuente al destino de la conexión estaba entre 5 y 316, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención la presencia de un outlier de valor máximo de 21.945.520 bytes siendo esta conexión maliciosa.

1.3.- 'dst_bytes'

El método "describe" ya apuntaba que el grueso de los valores del número de bytes de datos desde el destino a la fuente de la conexión estaba entre 5 y 1980, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención la presencia de un outlier de valor máximo de 5.150.938 bytes que al igual que pasaba con el outlier de bytes desde la fuente al destino también corresponde a una conexión maliciosa.

1.4.- 'wrong_fragment'

El método "describe" ya apuntaba que el grueso de los valores del número de frágmentos erróneos de la conexión son cero, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención la presencia de outliers de valor 1 y 3 que corresponden a conexiones maliciosas con una proporción mucho mayor que la del conjunto.

Hasta aquí parece constatarse la pauta de que los valores atípicos están asociados mayoritariamente con conexiones maliciosas.

1.5.- 'urgent'

El método "describe" ya apuntaba que el grueso de los valores del número de paquetes urgentes de la conexión son cero, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención la presencia de dos outliers de valor 2 que corresponden a conexiones buenas.

1.6.- 'hot'

El método "describe" ya apuntaba que el grueso de los valores del número de indicadores hot de la conexión son cero, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención que los outliers a partir de valores en torno a 2 conexiones hot correspondan todos a conexiones buenas.

Se aprecia en los límites marcados por la gráfica de fallidas hasta valores 2, pero se pasa a comprobar esta afirmación por rigor y veracidad.

Tanto urgent como hot rompen la tendencia que paracía darse respecto a los outliers de lo analizado hasta este punto, ya que para estas dos variables los outliers corresponden en mayor medida a conexiones buenas y no a maliciosas.

1.7.- 'num_failed_logins'

El método "describe" ya apuntaba que el grueso de los valores del número de intentos de login fallidos de la conexión son cero, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención que de los outliers con valores 1, 2 y 3, los valores 2 y 3 correspondan a conexiones buenas y que los valores de 1 intento de login fallido correspondan a conexiones maliciosas con una proporción mucho mayor que la del conjunto.

1.8.- 'num_compromised'

El método "describe" ya apuntaba que el grueso de los valores del número de condiciones comprometidas de la conexión son cero, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención el valor extremo máximo de 7479 condiciones comprometidas y que los outliers a partir de valores en torno a 2 correspondan todos a conexiones buenas (muy similar a lo que sucedía con el número de indicadores hot a partir también de 2).

Se aprecia en los límites marcados por la gráfica de fallidas hasta valores 2, pero se pasa a comprobar esta afirmación por rigor y veracidad.

1.9.- 'num_root'

El método "describe" ya apuntaba que el grueso de los valores del número de accesos raíz de la conexión son cero, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención el valor extremo máximo de 7468 accesos raíz y que los outliers a partir de valores en torno a 1 correspondan todos a conexiones buenas (muy similar a lo que sucedía con el número de indicadores hot y condiciones comprometidas a partir de 2).

1.10.- 'num_file_creations'

El método "describe" ya apuntaba que el grueso de los valores del número de operaciones de creación de fichero de la conexión son cero, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención que los outliers correspondan todos a conexiones buenas (muy similar a lo que sucedía con el número de indicadores hot, condiciones comprometidas y accesos raíz).

1.11.- 'num_shells'

El método "describe" ya apuntaba que el grueso de los valores del número de shell prompts de la conexión son cero, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención que los outliers correspondan todos a conexiones buenas (muy similar a lo que sucedía con el número de indicadores hot, condiciones comprometidas, accesos raíz y operaciones de creación de fichero).

1.12.- 'num_access_files'

El método "describe" ya apuntaba que el grueso de los valores del número de access files de la conexión son cero, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención que los outliers mayores a 1 access file correspondan todos a conexiones buenas (muy similar a lo que sucedía con el número de indicadores hot, condiciones comprometidas, accesos raíz, operaciones de creación de fichero y shell prompts).

1.13.- 'count'

El método "describe" ya apuntaba que el grueso de los valores del número de conexiones count de la conexión estaba entre 1 y 16, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención que en lo referente a los outliers la correspondencia de éstos con conexiones maliciosas se presenta en una proporción mucho mayor que la del conjunto.

1.14.- 'srv_count'

El método "describe" ya apuntaba que el grueso de los valores del número de conexiones srv count de la conexión estaba entre 2 y 18, por lo que es lógico que el grueso de las conexiones buenas y maliciosas se encuentren también en torno a estos valores. Llama la atención que en lo referente a los outliers la correspondencia de éstos con conexiones maliciosas se presenta de manera más patente, a diferencia de para el número de conexiones count, sólo a partir de valores en torno a 300 conexiones en una proporción mucho mayor que la del conjunto (para el número de conexiones srv count era más patente para todo el rango de outliers).

2.- Valores 0 o 1 (y 2!: 2.7.- 'su_attempted')

2.1.- 'protocol_type': tcp, udp e icmp

2.2.- 'service': http, private, domain u, smtp, ftp data, telnet, ftp y other.

Llama la atención que para valores 0 http la proporción de conexiones maliciosas es mayor que la del conjunto.

Llama la atención que para valores 1 private la proporción de conexiones maliciosas es mayor que la del conjunto.

Llama la atención que para valores 1 other la proporción de conexiones maliciosas es mayor que la del conjunto.

2.3.- 'flag': OTH (el resto de flags sólo con valores cero, OTH es el único flag con valores 0 y 1).

2.4- 'land'

2.5.- 'logged_in'

Llama la atención que para valores 0 logged in la proporción de conexiones maliciosas es mayor que la del conjunto.

2.6.- 'root_shell'

2.7.- 'su_attempted'

Segun la información oficial del dataset: su_attempted toma el valor 1 si "su root" command attempted y 0 en cualquier otro caso. Si se toma esta afirmación como cierta, los 15 valores 2 serían por lo tanto erróneos. No se dispone de un criterio para saber si los 2 corresponden a ceros, unos o a ambos y en qué proporción, por lo que se toma la decisión de no sustituirlos por ceros, unos o por alguna proporción de ambos. Es posible que futuros datos pudieran llegar por alguna razón con valores 2, por lo que se decide dejarlos.

2.8.- 'is_guest_login'

3.- Porcentajes en tanto por uno.

3.1.- 'serror_rate', 'srv_serror_rate', 'rerror_rate', 'srv_rerror_rate'.

3.2.- 'same_srv_rate', 'diff_srv_rate', 'srv_diff_host_rate'.

Matriz de correlación: se comienza con un primer acercamiento numérico para su análisis y se continua con un posterior acercameinto visual:

Llaman la atención determinadas variables que arrojan resultados no posibles de calcular (NaN). Se procede a su análisis:

La variable "flagS0" presenta todos sus valores cero. Se procede de igual manera con "flagREJ", "flagRSTO", "flagRSTO" y "flag__OTH":

Se eliminan estas variables del análisis de correlación:

El mapa de calor es una forma visual muy útil para conocer las variables y sus relaciones. A primera vista hay diversas variables que llaman la atención: 'protocol_typetcp' vs 'protocol_typeudp', 'protocol_typetcp' vs 'servicedomain_u', 'protocol_typeudp' vs 'servicedomain_u', 'protocol_typeudp' vs 'logged_in', 'serviceftp' vs 'hot', 'service__ftp' vs 'is_guest_login', 'hot' vs 'is_guest_login', 'num_compromised' vs 'num_root', 'count' vs 'srv_count', 'serror_rate' vs 'srv_serror_rate', 'rerror_rate' vs 'srv_rerror_rate', 'serror_rate' vs 'same_srv_rate' y 'srv_serror_rate' vs 'same_srv_rate'. En estos casos parece haber una correlación significativa (positiva o negativa). En realidad son tan fuertes que podrían indicar multicolinealidad, es decir, que básicamente podrían ofrecer la misma información.

¿Se deben eliminar las variables detectadas que presentan valores siempre igual a cero? Se comprueba para "nuevas_conexiones_csv" qué valores se tienen para estas variables:

Para las tres conexiones se tiene valores también igual a cero. Esta confirmación junto con el hecho de la no aportación de valor añadido de variables con valor siempre constante, precipita la decisión de eliminarlas:

3. Separación en train y test

Puesto que sklearn (y en general en machine learning) las máquinas sólo entienden de números, vamos a convertir la columna type (que ahora mismo son strings con los nombres "normal" y "attack") a números correspondientes (por ejemplo: 0 para normal y 1 para attack). De manera coloquial se suele decir que a scikit-learn no le gustan los strings.

Se tiene por lo tanto en este momento un DataFrame puramente numérico, y se procede a separar en train y test:

Se han considerado como razonables los porcentajes del 80% de los valores para uso de entrenamiento y validación del modelo y del 20% para su posterior testeo.

Como buena praxis se empleará una semilla pseudoaleatoria para la selección de los datos a partir de este momento. Esto cobra especial relevancia a la hora de poder establecer comparativas entre los modelos que se vayan a probar de cara a la elección del modelo ganador: random_state = 101

¿Cómo de bien o de mal mantiene la proporción de conexiones buenas y maliciosa tanto el conjunto de datos de entrenamiento y validación como el de test? Se recuerda que la proporción era en el global del dataset de normal: 93.590662 % y attack: 6.409338 %

Las dos clases están representadas en una proporción razonablemente buena. Se prueba cambiando las semillas (2001, 753, 777) pero no se consiguen valores a priori mejores. La literatura consultada al respecto tampoco anima al cambio, a no ser que la proporción distara mucho de la de origen, poniendo así en jaque una representatividad adecuada (no es el caso).

4. Preprocesado del conjunto de train

Antes de realizar modelos o validaciones cruzadas se deben plantear una serie de decisiones sobre los datos:

1.- ¿Descartar variables directamente por alta correlación? Se toma como premisa de trabajo no eliminar variables por alta correlación.

2.- ¿Quitar outliers? Se toma como premisa de trabajo no eliminar ouliers.

3.- ¿Estandarizar las features? Sí se realizará.

4.- ¿Seleccionar features / hacer reducción de dimensionalidad? Sí se realizará la selección de features pero al tener 45 variables no tiene sentido relizar una reducción de dimensionalidad.

Llegados a este punto, a partir de ahora el objetivo es probar un gran número de combinaciones de modelos e hiperparámetros.

5. Creación de Pipelines

6. Grids de hiperparámetros

Para cada una de las Pipelines, se seleccionan los hiperparámetros a probar.

Se procede al ensamblado de todas estas posibles combinaciones.

7. Grid Search y selección de modelo

Se construirá cada GridSearchCV con su Pipeline y sus hiperparámetros correspondientes. Al presentar desbalanceo no se va a utilizar accuracy, sino F1-score o curva ROC. El área bajo la curva ROC es muy buena métrica: AUC (area under the curve): entre 0.5 (malo) y 1 (el mejor): es muy visual. También la métrica F1-score se usa mucho: cuanto más cercano a 1 mejor.

Para poder comparar todos los modelos de igual manera es importante establecer una Cross Validation idéntica para todos, con una misma semilla aleatoria.

Se introducen todos los GridSearchCV en un diccionario de cara a los resultados tener los pares clave - valor, es decir, descripción : el GridSearchCV.

Se itera por cada par clave - valor del diccionario todos_los_grid_search, y por cada par se lanza su GridSearchCV.

Se pasa a analizar cuál es el mejor resultado obtenido en Cross Validation de cada GridSearchCV.

Para una mejor visualización de los resultados obtenidos, se crea un DataFrame ordenándolo de mejor a peor resultado.

El modelo ganador, como mejor GridSearchCV con el mejor roc_auc en validación cruzada, es Random Forest Classifier con un mejor score del 85.23%.

Se tiene dentro de este GridSearchCV, que el mejor modelo es el siguiente.

Se re-entrena el modelo.

8. Mediciones sobre el conjunto de test

Una vez que el modelo ganador ya ha sido seleccionado y entrenado con todo el conjunto de train, se procede a ver cómo predice en el conjunto de test. De esta forma, se sabrá qué resultados podemos esperar a futuro de dicho modelo.

Se saca el roc_auc en el conjunto de test (% de observaciones que el modelo clasifica bien), y la matriz de confusión.

8.1.- Roc_acu %

8.2.- Matriz de confusión

Se transforma mediante un dataframe en algo visualmente más atractivo.

Se realiza un mapa de calor para la matriz de confusión.

El modelo ganador o mejor modelo obtenido predecirá a futuro en torno al 97% de las buenas como buenas y al 69% de las maliciosa como maliciosas. Es lo que se puede esperar del modelo a futuro.

9. Guardar el modelo/pipeline para futuros usos y hacer predicciones

9.1.- Guardado

9.2.- Cargado

9.3.- Comprobación de que arroja el mismo resultado de test.

10. Generar predicciones para tres nuevas observaciones

Las variables no están en el mismo orden, es decir, el orden de features_n_c_limpio_dataset es distinto al de features.

Se procede a solucionarlo de cara a una correcta realización de las predicciones.

Se comprueba.

The End